#define GETTEXT_PACKAGE "gsmon"
#include <glib/gi18n-lib.h>
#include <string.h>
#include <glib/gprintf.h>
#include "gsm-watcher-source.h"

static void
format_time (float time, gchar *buffer)
{
	glong t = time;
	
	if (t < 0) {
		g_sprintf (buffer, "?:??:??");
		return;
	}
	
	gint seconds = t % 60;
	gint minutes = (t / 60) % 60;
	gint hours = t / (60 * 60);
	
	g_sprintf (buffer, "%d:%02d:%02d", hours, minutes, seconds);
}

static void gsm_watcher_source_update (GsmWatcher *watcher);
static gboolean gsm_watcher_process_respond (GsmWatcher *watcher,
		const gchar *buffer, gsize size);

G_DEFINE_TYPE (GsmWatcherSource, gsm_watcher_source, GSM_TYPE_WATCHER)

static void
gsm_watcher_source_class_init (GsmWatcherSourceClass *klass)
{
	GsmWatcherClass *watcher_class = GSM_WATCHER_CLASS (klass);
	
	watcher_class->default_port = 27015;
	watcher_class->update = gsm_watcher_source_update;
	watcher_class->process_respond = gsm_watcher_process_respond;
}

static void
gsm_watcher_source_init (GsmWatcherSource* view)
{
	static GsmPlayerParameter player_parameters[] = {
		{N_("Kills"),	G_TYPE_INT},
		{N_("Time"),		G_TYPE_STRING},
		{NULL,			G_TYPE_INVALID}
	};
	
	GSM_WATCHER (view)->params = player_parameters;
}

GsmWatcher *
gsm_watcher_source_new (const gchar *address)
{
	return g_object_new (GSM_TYPE_WATCHER_SOURCE, "address", address, NULL);
}

static void
gsm_watcher_source_update (GsmWatcher *watcher)
{
	GsmWatcherSource *source = GSM_WATCHER_SOURCE (watcher);
	
	char a2s_getchallenge_request[5] = "\xFF\xFF\xFF\xFF\x57";
	char a2s_info_request[25] = "\xFF\xFF\xFF\xFF\x54Source Engine Query";
	char a2s_players_request[9] = "\xFF\xFF\xFF\xFF\x55\xFF\xFF\xFF\xFF";
	
	if (!source->challenge)
		gsm_watcher_send (watcher, a2s_getchallenge_request, 5);
	
	gsm_watcher_send (watcher, a2s_info_request, 25);
	*((guint32*) &a2s_players_request[5]) = source->challenge;
	gsm_watcher_send (watcher, a2s_players_request, 9);
}

static gchar *
parser_read_int8 (gchar *p, gint8 *dest)
{
	if (dest) *dest = *(gint8 *) p;
	return p + 1;
}

static gchar *
parser_read_int16 (gchar *p, gint16 *dest)
{
	if (dest) *dest = *(gint16 *) p;
	return p + 2;
}

static gchar *
parser_read_int32 (gchar *p, gint32 *dest)
{
	if (dest) *dest = *(gint32 *) p;
	return p + 4;
}

static gchar *
parser_read_string (gchar *p, gchar *dest)
{
	if (dest) strcpy (dest, p);
	return p + strlen (p) + 1;
}
/*
static void
parse_goldsource_server_info (GsmWatcher *watcher, const gchar *buffer,
	gsize length)
{
	gint8 proto_ver;
	gint8 dedicated;
	gchar *os;
	gchar *game_folder;
	gint8 is_mod;
	char *p = (char *)buffer;
	
	p = parser_read_string (p, NULL);					// Server IP... skip
	p = parser_read_string (p, watcher->name);			// Server name
	p = parser_read_string (p, watcher->map);			// map name
	game_folder = p; p += strlen (p) + 1;				// Game folder
	p = parser_read_string (p, NULL);					// Game name... skip
	p = parser_read_int8 (p, &watcher->num_players);	// Number of players
	p = parser_read_int8 (p, &watcher->max_players);	// Maximum players
	p = parser_read_int8 (p, &proto_ver);				// Protocol version
	p = parser_read_int8 (p, &dedicated);				// Dedicated
	
	switch (*((guint8*)p++)) {
		case 'l': os = "Linux"; break;
		case 'w': os = "Windows"; break;
		default: os = "Unknown";
	}
	
	p = parser_read_int8 (p, NULL);						// Password... skip
	p = parser_read_int8 (p, &is_mod);					// Is mod
	p = parser_read_int8 (p, NULL);						// Secure
	p = parser_read_int8 (p, NULL);						// Number of bots
	
	if (is_mod) {
		p = parser_read_string (p, NULL);				// URLInfo
		p = parser_read_string (p, NULL);				// URLDL
		p = parser_read_int8 (p, NULL);					// Null
		p = parser_read_int32 (p, NULL);				// ModVersion
		p = parser_read_int32 (p, NULL);				// ModSize
		p = parser_read_int8 (p, NULL);					// SvOnly
		p = parser_read_int8 (p, NULL);					// ClDLL
	}
	
	g_sprintf (watcher->version, "Unknown (%s%s, Protocol %d)", os,
			dedicated == 'd' ? ", Dedicated" : "", proto_ver);
	
	if (!strcmp (game_folder, "valve")) {
		strcpy (watcher->game, "Half-Life 1");
		watcher->icon = "hl1.png";
	}
}
*/
static gboolean
parse_source_server_info (GsmWatcher *watcher, const gchar *buffer,
		gsize length)
{
	gint8 proto_ver;
	gint16 app_id;
	gchar *game_desc;
	gchar *os;
	gint8 dedicated;
	gchar game_ver[32];
	gchar *p = (gchar *)buffer;
	
	p = parser_read_int8 (p, &proto_ver);				// Protocol version
	p = parser_read_string (p, watcher->name);			// Server name
	p = parser_read_string (p, watcher->map);			// Map name
	p = parser_read_string (p, NULL);					// Game folder... skip
	game_desc = p; p += strlen (p) + 1;					// Game name
	p = parser_read_int16 (p, &app_id);					// Application ID
	p = parser_read_int8 (p, &watcher->num_players);	// Number of players
	p = parser_read_int8 (p, &watcher->max_players);	// Maximum players
	p = parser_read_int8 (p, NULL);						// Number of bots... skip
	p = parser_read_int8 (p, &dedicated);				// Dedicated
	
	switch (*((guint8*)p++)) {
		case 'l': os = "Linux"; break;
		case 'w': os = "Windows"; break;
		default: os = "Unknown";
	}
	
	p = parser_read_int8 (p, NULL);						// Has password... skip
	p = parser_read_int8 (p, NULL);						// Secure... skip
	p = parser_read_string (p, game_ver);				// Game version
	
	
	switch (app_id) {
	case 10: // Counter-Strike
		strcpy (watcher->game, "Counter-Strike");
		watcher->icon = "cs.png";
		break;
	case 30: // Day od Defeat
		strcpy (watcher->game, game_desc);
		watcher->icon = "dod.png";
		break;
	case 80: // Counter-Strike: Condition Zero
		strcpy (watcher->game, "Counter-Strike: Condition Zero");
		watcher->icon = "czero.png";
		break;
	case 240: // Counter-Strike: Source
		strcpy (watcher->game, game_desc);
		watcher->icon = "css.png";
		break;
	case 300: // Day of Defeat: Source
		strcpy (watcher->game, game_desc);
		watcher->icon = "dods.png";
		break;
	case 320: // Half-Life 2: Deathmatch
		g_sprintf (watcher->game, "Half-Life 2: %s", game_desc);
		watcher->icon = "hl2dm.png";
		break;
	case 440: // Team Fortress 2
		strcpy (watcher->game, "Team Fortress 2");
		watcher->icon = "tf2.png";
		break;
	case 500: // Left 4 Dead
		g_sprintf (watcher->game, "Left 4 Dead (%s)", &game_desc[6]);
		watcher->icon = "l4d.png";
		break;
	case 550: // Left 4 Dead 2
		strcpy (watcher->game, game_desc);
		watcher->icon = "l4d2.png";
		break;
	default:
		g_sprintf (watcher->game, "Source-Based [%d] %s", app_id, game_desc);
		watcher->icon = "source.png";
	}
	
	if (*game_ver)
		g_sprintf (watcher->version, "%s (%s%s, Protocol %d)", game_ver, os,
				dedicated ? ", Dedicated" : "", (guint8)proto_ver);
	else
		strcpy (watcher->version, "Unknown");
	
	return TRUE;
}

static void
parse_players_info (GsmWatcher *watcher, const gchar *buffer,
		gsize length)
{
	GSList *players = NULL;
	gchar *p = (gchar *) buffer;
	gint i;
	gint8 count;
	
	p = parser_read_int8 (p, &count);				// Number of players
	
	for (i = 0; i < count; i++) {
		GsmPlayer *player = g_slice_new (GsmPlayer);
		player->watcher = watcher;
		GValue *values = player->values = g_new0 (GValue, 3);
		
		p = parser_read_int8 (p, NULL);				// Number
		p = parser_read_string (p, player->name);	// Name
		
		gint32 kills;
		g_value_init (&values[0], G_TYPE_INT);
		p = parser_read_int32 (p, &kills);	// Kills
		g_value_set_int (&values[0], kills);
		
		// Time
		gchar text[32];
		format_time (*((float *) p), text);
		g_value_init (&values[1], G_TYPE_STRING);
		g_value_set_string (&values[1], text);
		p += 4;
		
		players = g_slist_append (players, player);
	}
	
	gsm_watcher_update_players_list (watcher, players);
}

static gboolean
gsm_watcher_process_respond (GsmWatcher *watcher, const gchar *buffer, gsize size)
{
	GsmWatcherSource *source = GSM_WATCHER_SOURCE (watcher);
	
	if (*((gint32 *) buffer) == -1) {
		switch (buffer[4]) {
		case 'j': // ping
			break;
		case 'A': // challenge
			source->challenge = *((guint32 *) &buffer[5]);
			break;
		case 'I': // info from Source
			parse_source_server_info (watcher, &buffer[5], size);
			break;
//		case 'm': // info from GoldSource
//			parse_goldsource_server_info (watcher, &buffer[5], size);
//			break;
		case 'D': // players
			parse_players_info (watcher, &buffer[5], size);
			break;
		case 'E': // rules
			break;
		}
		
	} else {
		return FALSE;
	}

	return TRUE;
}